﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.Mirror.CliMetadata.Tables;
using System.Globalization;
using System.Configuration.Assemblies;
using Kokomo.Mirror.CliMetadata.Signatures;
using System.IO;
using Kokomo.Mirror.PeFormat;
using System.Runtime.InteropServices;

namespace Kokomo.Mirror.CliMetadata
{
	public class CliModule : Module, IExtendedModuleInfo
	{
		internal CliAssembly CliAssembly { get; private set; }
		internal PeModule peModule;

		private MetadataContainer _metadata;
		public MetadataContainer Metadata { get { return this._metadata; } }

		public CliModule(
			CliAssembly assembly,
			PeModule peModule,
			int metadataToken
			)
		{
			if ((object)assembly == null) throw new ArgumentNullException("assembly");
			if (peModule == null) throw new ArgumentNullException("pe");

			this.CliAssembly = assembly;
			this.peModule = peModule;
			this._path = peModule.FileName;
			this._metadataToken = metadataToken;

			SmallVersion metadataStreamVersion = peModule.Cor20MetadataStreamHeader.Version;
			this._mdStreamVersion = (metadataStreamVersion.Major << 16) | metadataStreamVersion.Minor;

			MetadataContainer metadata = peModule.MetadataContainer;
			this._metadata = metadata;
			this._moduleRow = metadata.ModuleTable[0];

			this._signatureTable = new Dictionary<int, WeakReference>();

			this._implementedTypeCache = new Dictionary<int, WeakReference>(this._metadata.TypeDefTable.RowCount);
			this._implementedMethodCache = new Dictionary<int, WeakReference>(this._metadata.MethodDefTable.RowCount);
			this._methodRefCache = new Dictionary<int, ModuleMemberReference>(this._metadata.MemberRefTable.RowCount);

			this._methodSemanticsLookup = metadata.MethodSemanticsTable.ToLookup(row => row.Association.Token);
			this._interfaceImplTokenLookup = metadata.InterfaceImplTable.ToLookup(row => row.Class.RowNumber, row => row.Interface.Token);
			this._methodImplLookup = metadata.MethodImplTable.ToLookup(row => row.Class.Token);
			this._customAttributeLookup = metadata.CustomAttributeTable.ToLookup(r => r.Parent.Token);
			this._genericParamsLookup = metadata.GenericParamTable
				.Select((r, i) => new { Row = r, RowNumber = i + 1 })
				.ToLookup(r => r.Row.Owner.Token, r => r.RowNumber
				);
			this._genericParamConstraintsLookup = metadata.GenericParamConstraintTable
				.ToLookup(r => r.Owner.Token);
			this._typeNameIndex = metadata.TypeDefTable
				.Select((row, index) => new { Row = row, RowNumber = index + 1 })
				.ToLookup(
					(entry) => GetFullTypeName(entry.Row),
					entry => entry.RowNumber,
					StringComparer.InvariantCultureIgnoreCase
					);
			this._nestedTypeLookup = metadata.NestedClassTable.ToLookup(
				row => row.EnclosingClass.RowNumber,
				row => row.NestedClass.RowNumber
				);
			this._constantLookup = metadata.ConstantTable.ToLookup(
				row => row.Parent.Token
				);
			this._implMapLookup = metadata.ImplMapTable.ToLookup(
				row => row.MemberForwarded.Token
				);
			this._fieldRvaLookup = metadata.FieldRvaTable.ToLookup(
				row => row.Field.Token, row => row.Rva
				);

			this._nestedClassSearcher = new BinaryListSearcher<NestedClassRow, int>(
				this._metadata.NestedClassTable.Rows,
				r => r.NestedClass.RowNumber
				);
			this._eventMapSearcher = new BinaryListSearcher<EventMapRow, int>(
				this._metadata.EventMapTable.Rows,
				r => r.Parent.RowNumber
				);
			this._propertyMapSearcher = new BinaryListSearcher<PropertyMapRow, int>(
				this._metadata.PropertyMapTable.Rows,
				r => r.Parent.RowNumber
				);
			this._classLayoutSearcher = new BinaryListSearcher<ClassLayoutRow, int>(
				this._metadata.ClassLayoutTable.Rows,
				r => r.Parent.Token
				);
		}

		private string GetFullTypeName(TypeDefRow typeDefRow)
		{
			//if (typeDefRow == null) throw new ArgumentNullException("typeDefRow");

			string namespaceName = this._metadata.GetString(typeDefRow.TypeNamespace);
			string name = this._metadata.GetString(typeDefRow.TypeName);

			if (!string.IsNullOrEmpty(namespaceName))
				return namespaceName + Type.Delimiter + name;
			else
				return name;
		}

		#region Module info
		private ModuleRow _moduleRow;

		public override Assembly Assembly
		{
			get { return this.CliAssembly; }
		}

		private string _path;
		public override string FullyQualifiedName
		{
			get { return this._path; }
		}

		private int _mdStreamVersion;
		public override int MDStreamVersion
		{
			get { return this._mdStreamVersion; }
		}

		private int _metadataToken;
		public override int MetadataToken
		{
			get { return this._metadataToken; }
		}
		public override Guid ModuleVersionId
		{
			get { return this._moduleRow.Mvid.Guid; }
		}
		public override string Name
		{
			get { return Path.GetFileName(this._path); }
		}
		public override string ScopeName
		{
			get { return this._metadata.GetString(this._moduleRow.Name); }
		}
		internal int EntryPointToken
		{
			get { return (int)this.peModule.Cor20Header.EntryPointTokenOrRva; }
		}

		public override void GetPEKind(
			out PortableExecutableKinds peKind,
			out ImageFileMachine machine
			)
		{
			CorImageFlags imageFlags = this.peModule.Cor20Header.Flags;
			peKind = 0;
			if (imageFlags.HasFlag(CorImageFlags.IlOnly)) peKind |= PortableExecutableKinds.ILOnly;
			if (imageFlags.HasFlag(CorImageFlags.Requires32Bit)) peKind |= PortableExecutableKinds.Required32Bit;
			if (this.peModule.Is64Bit) peKind |= PortableExecutableKinds.PE32Plus;

			machine = (ImageFileMachine)this.peModule.NewExeFileHeader.Machine;
		}
		#endregion

		#region Metadata helpers
		private ILookup<int, MethodSemanticsRow> _methodSemanticsLookup;
		private ILookup<int, int> _interfaceImplTokenLookup;
		private ILookup<int, MethodImplRow> _methodImplLookup;
		private ILookup<int, int> _genericParamsLookup;
		private ILookup<int, int> _nestedTypeLookup;
		private ILookup<int, GenericParamConstraintRow> _genericParamConstraintsLookup;
		private ILookup<int, ConstantRow> _constantLookup;
		private ILookup<int, ImplMapRow> _implMapLookup;
		private ILookup<int, int> _fieldRvaLookup;
		private BinaryListSearcher<NestedClassRow, int> _nestedClassSearcher;
		private BinaryListSearcher<EventMapRow, int> _eventMapSearcher;
		private BinaryListSearcher<PropertyMapRow, int> _propertyMapSearcher;
		private BinaryListSearcher<ClassLayoutRow, int> _classLayoutSearcher;

		internal object QueryConstantValue(int ownerToken)
		{
			ConstantRow constantRow = this._constantLookup[ownerToken].FirstOrDefault();
			if (constantRow == null) return DBNull.Value;
			if (constantRow.Value.IsNull) return null;

			object value = constantRow.ToValue(this._metadata);

			return value;
		}
		internal int QueryFieldRva(int fieldToken)
		{
			return this._fieldRvaLookup[fieldToken].FirstOrDefault();
		}
		internal Type[] QueryInterfacesForTypeDef(
			CliImplementedType type
			)
		{
			if ((object)type == null) throw new ArgumentNullException("type");

			CodedIndex ownerIndex = CodedIndex.FromToken(type.MetadataToken);
			Type[] genericTypeArguments = type.GetGenericArguments();
			return this._interfaceImplTokenLookup[ownerIndex.RowNumber].Select(token => this.ResolveType(token, genericTypeArguments, null)).ToArray();
		}
		internal InterfaceMapping QueryInterfaceMap(
			CliImplementedType type,
			Type interfaceType
			)
		{
			if ((object)type == null) throw new ArgumentNullException("type");
			if ((object)interfaceType == null) throw new ArgumentNullException("interfaceType");

			MethodInfo[] interfaceMethods = interfaceType.GetMethods(BindingFlags.Instance | BindingFlags.Public);

			InterfaceMapping map;
			Type baseType = type.BaseType;
			if (
				((object)baseType != null)
				&& baseType.GetInterfaces().Contains(interfaceType)
				)
			{
				map = baseType.GetInterfaceMap(interfaceType);
			}
			else
			{
				map = new InterfaceMapping()
				{
					TargetType = type,
					InterfaceType = interfaceType,
					InterfaceMethods = interfaceMethods
				};
			}

			MethodInfo[] implementations = new MethodInfo[interfaceMethods.Length];
			MethodImplRow[] methodImplRows = this._methodImplLookup[type.MetadataToken].ToArray();
			Type[] genericInterfaceArgs = interfaceType.GetGenericArguments();
			Type[] genericTypeArgs = type.GetGenericArguments();
			foreach (var methodImplRow in methodImplRows)
			{
				Type owner = this.GetMethodOwner(methodImplRow.MethodDeclaration, genericInterfaceArgs, null);
				if (MemberEqualityComparer.Equal(owner, interfaceType))
				{
					MethodInfo implementation = (MethodInfo)this.ResolveMethod(methodImplRow.MethodBody.Token, genericTypeArgs, null);
					MethodInfo interfaceMethod = (MethodInfo)this.ResolveMethod(methodImplRow.MethodDeclaration.Token, genericTypeArgs, null);
					int declarationIndex = Array.FindIndex(interfaceMethods, m => m.MetadataToken == interfaceMethod.MetadataToken);
					implementations[declarationIndex] = implementation;
				}
			}

			for (int i = 0; i < implementations.Length; i++)
			{
				if ((object)implementations[i] == null)
				{
					MethodInfo interfaceMethod = interfaceMethods[i];
					Type[] parameterTypes = Array.ConvertAll(interfaceMethod.GetParameters(), p => p.ParameterType);

					MethodInfo implementation = type.GetMethod(
						interfaceMethod.Name,
						parameterTypes,
						null
						);
					implementations[i] = implementation;
				}
			}

			map.TargetMethods = implementations;

			return map;
		}
		internal MethodBase[] QueryMethodsForTypeDef(
			CliImplementedType declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			CodedIndex ownerIndex = CodedIndex.FromToken(declaringType.MetadataToken);
			MetadataRowRange<MethodDefRow> methodRowRange = this._metadata.TypeDef_MethodDef.GetChildRangeFor(ownerIndex.RowNumber);
			MethodBase[] methods = new MethodBase[methodRowRange.RowCount];
			for (int i = 0; i < methodRowRange.RowCount; i++)
			{
				MethodBase method = this.LoadMethodDef(i + methodRowRange.FirstRowNumber, declaringType, reflectedType);
				methods[i] = method;
			}
			return methods;
		}
		internal EventInfo[] QueryEventsForTypeDef(
			CliImplementedType declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			CodedIndex ownerIndex = CodedIndex.FromToken(declaringType.MetadataToken);
			int eventMapRowNumber = this._eventMapSearcher.FindIndex(ownerIndex.RowNumber) + 1;
			if (eventMapRowNumber == 0) return new EventInfo[0];

			MetadataRowRange<EventRow> eventRange = this._metadata.EventMap_Event.GetChildRangeFor(eventMapRowNumber);
			EventInfo[] events = new EventInfo[eventRange.RowCount];
			for (int i = 0; i < eventRange.RowCount; i++)
			{
				EventInfo eventInfo = this.LoadEvent(
					i + eventRange.FirstRowNumber,
					declaringType,
					reflectedType
					);
				events[i] = eventInfo;
			}
			return events;
		}
		internal FieldInfo[] QueryFieldsForTypeDef(
			CliImplementedType declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			CodedIndex ownerIndex = CodedIndex.FromToken(declaringType.MetadataToken);
			MetadataRowRange<FieldRow> fieldRange = this._metadata.TypeDef_Field.GetChildRangeFor(ownerIndex.RowNumber);
			FieldInfo[] fields = new FieldInfo[fieldRange.RowCount];
			for (int i = 0; i < fieldRange.RowCount; i++)
			{
				FieldInfo field = this.LoadField(
					i + fieldRange.FirstRowNumber,
					declaringType,
					reflectedType
					);
				fields[i] = field;
			}
			return fields;
		}
		internal PropertyInfo[] QueryPropertiesForTypeDef(
			CliImplementedType declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			CodedIndex ownerIndex = CodedIndex.FromToken(declaringType.MetadataToken);
			int propertyMapRowNumber = this._propertyMapSearcher.FindIndex(ownerIndex.RowNumber) + 1;
			if (propertyMapRowNumber == 0) return new PropertyInfo[0];

			MetadataRowRange<PropertyRow> propertyRange = this._metadata.PropertyMap_Property.GetChildRangeFor(propertyMapRowNumber);
			PropertyInfo[] properties = new PropertyInfo[propertyRange.RowCount];
			for (int i = 0; i < propertyRange.RowCount; i++)
			{
				PropertyInfo property = this.LoadProperty(
					i + propertyRange.FirstRowNumber,
					declaringType,
					reflectedType
					);
				properties[i] = property;
			}
			return properties;
		}
		internal CliImplementedType[] QueryNestedTypesForTypeDef(
			CliImplementedType declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			CodedIndex ownerIndex = CodedIndex.FromToken(declaringType.MetadataToken);
			int[] nestedTypeRowNumbers = this._nestedTypeLookup[ownerIndex.RowNumber].ToArray();
			CliImplementedType[] nestedTypes = new CliImplementedType[nestedTypeRowNumbers.Length];
			for (int i = 0; i < nestedTypeRowNumbers.Length; i++)
			{
				CliImplementedType nestedType = this.LoadTypeDef(
					nestedTypeRowNumbers[i],
					declaringType,
					reflectedType
					);
				nestedTypes[i] = nestedType;
			}
			return nestedTypes;
		}
		internal ParameterInfo[] QueryParameters(
			int methodDefRowNumber,
			MethodBase declaringMethod,
			MethodDefSignature methodSignature,
			out ParameterInfo returnParameter
			)
		{
			if (methodDefRowNumber < 1) throw new ArgumentNullException("methodDefRowNumber");
			if (methodDefRowNumber > this._metadata.MethodDefTable.RowCount) throw new ArgumentOutOfRangeException("methodDefRowNumber", methodDefRowNumber, null);

			MetadataRowRange<ParamRow> paramRowRange = this._metadata.MethodDef_Param.GetChildRangeFor(methodDefRowNumber);
			ParamRow[] paramRows = paramRowRange.GetRows();

			int parameterCount = methodSignature.parameters.Length;
			returnParameter = null;
			bool hasReturn = methodSignature.HasReturnValue();
			ParameterInfo[] parameters = new ParameterInfo[parameterCount];
			for (int i = (hasReturn ? 0 : 1); i <= parameterCount; i++)
			{
				ParamRow paramRow = null;
				int paramRowNumber = 0;
				// UGLY
				for (int j = 0; j < paramRows.Length; j++)
				{
					ParamRow r = paramRows[j];
					if (r.Sequence == i)
					{
						paramRowNumber = paramRowRange.FirstRowNumber + j;
						paramRow = r;
					}
				}

				Param parameterSignature =
					(i == 0)
					? methodSignature.returnType
					: methodSignature.parameters[i - 1]
					;

				ParameterAttributes parameterAttributes = ParameterAttributes.None;
				string name = string.Empty;
				if (paramRow != null)
				{
					parameterAttributes = paramRow.Flags;
					name = this._metadata.GetString(paramRow.Name);
				}

				ParameterInfo parameter;
				parameter = new CliParameterInfo(
					new CliMemberBasicInfo(
						this,
						CodedIndex.TokenFromTableRow(MetadataTableNumber.Param, paramRowNumber)
						),
					name,
					parameterAttributes,
					i,
					parameterSignature
					) { owner = declaringMethod };
				if (i == 0)
					returnParameter = parameter;
				else
					parameters[i - 1] = parameter;
			}

			return parameters;
		}

		internal ILookup<MethodSemanticsAttributes, int> QueryMethodSemantics(int ownerToken)
		{
			return this._methodSemanticsLookup[ownerToken]
				.ToLookup(r => r.Semantics, r => r.Method.RowNumber);
		}
		internal IEnumerable<MethodInfo> QuerySemanticMethods(
			int memberToken,
			Type declaringType,
			Type reflectedType,
			MethodSemanticsAttributes semantics,
			bool nonPublic
			)
		{
			var methodSemanticLookup = this.QueryMethodSemantics(memberToken);
			int[] methodDefRowNumbers = methodSemanticLookup[semantics].ToArray();

			IEnumerable<MethodInfo> methods = methodDefRowNumbers.Select(n =>
				(MethodInfo)this.LoadMethodDef(
				n,
				declaringType,
				reflectedType
				))
				.Where(m => (nonPublic || m.IsPublic));
			return methods;
		}

		internal ClassLayoutRow QueryClassLayout(int ownerToken)
		{
			ClassLayoutRow classLayoutRow = this._classLayoutSearcher.FindItem(ownerToken);
			return classLayoutRow;
		}

		internal Type[] QueryGenericParams(
			CliMethodInfo declaringMethod
			)
		{
			if ((object)declaringMethod == null) throw new ArgumentNullException("declaringMethod");
			return this.QueryGenericParams(declaringMethod.MetadataToken, null, null, declaringMethod);
		}
		internal Type[] QueryGenericParams(
			Type declaringType,
			Type reflectedType
			)
		{
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");
			return this.QueryGenericParams(declaringType.MetadataToken, declaringType, reflectedType, null);
		}
		private Type[] QueryGenericParams(
			int ownerToken,
			Type declaringType,
			Type reflectedType,
			CliMethodInfo declaringMethod
			)
		{
			int[] genericParamsRowNumbers = this._genericParamsLookup[ownerToken].ToArray();
			if (genericParamsRowNumbers.Length == 0) return Type.EmptyTypes;

			Type[] genericParamTypes = new Type[genericParamsRowNumbers.Length];
			for (int i = 0; i < genericParamTypes.Length; i++)
			{
				int genericParamRowNumber = genericParamsRowNumbers[i];
				int genericParamToken = CodedIndex.TokenFromTableRow(MetadataTableNumber.GenericParam, genericParamRowNumber);
				GenericParamRow genericParamRow = this._metadata.GenericParamTable[genericParamRowNumber - 1];

				CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(this, genericParamToken)
				{
					reflectedType = reflectedType,
					declaringType = declaringType
				};
				CliGenericParamType paramType = new CliGenericParamType(
					genericParamRow,
					basicInfo,
					declaringMethod
					);
				genericParamTypes[i] = paramType;
			}

			return genericParamTypes;
		}
		internal Type[] QueryGenericParamConstraints(
			int ownerToken,
			Type[] genericTypeArguments,
			Type[] genericMethodArguments
			)
		{
			GenericParamConstraintRow[] constraints = this._genericParamConstraintsLookup[ownerToken].ToArray();
			if (constraints.Length == 0) return Type.EmptyTypes;

			Type[] types = Array.ConvertAll(constraints, c => this.ResolveType(c.Constraint.Token, genericTypeArguments, genericMethodArguments));
			return types;
		}
		#endregion

		#region Attributes
		private ILookup<int, CustomAttributeRow> _customAttributeLookup;

		internal CustomAttributeData[] GetCustomAttributesFor(int ownerToken)
		{
			CustomAttributeRow[] attributeRows = this._customAttributeLookup[ownerToken].ToArray();
			List<CustomAttributeData> attributes = new List<CustomAttributeData>(attributeRows.Length);
			for (int i = 0; i < attributeRows.Length; i++)
			{
				CustomAttributeData attribute = CliCustomAttributeData.Parse(this, attributeRows[i]);
				attributes.Add(attribute);
			}

			// Pseudoattributes
			ImplMapRow implMapRow = this._implMapLookup[ownerToken].FirstOrDefault();
			if (implMapRow != null)
			{
				PinvokeAttributes pinvokeAttributes = implMapRow.MappingFlags;
				Type dllImportType = typeof(DllImportAttribute);

				ModuleRefRow moduleRef = this._metadata.ModuleRefTable[implMapRow.ImportScope.RowNumber - 1];
				string moduleName = this._metadata.GetString(moduleRef.Name);

				List<CustomAttributeNamedArgument> namedArguments = new List<CustomAttributeNamedArgument>();
				// EntryPoint
				string entryPoint = this._metadata.GetString(implMapRow.ImportName);
				namedArguments.Add(new CustomAttributeNamedArgument(dllImportType.GetField("EntryPoint"), new CustomAttributeTypedArgument(typeof(string), entryPoint)));
				// SetLastError
				if (pinvokeAttributes.HasFlag(PinvokeAttributes.SupportsLastError))
					namedArguments.Add(new CustomAttributeNamedArgument(dllImportType.GetField("SetLastError"), new CustomAttributeTypedArgument(typeof(bool), true)));
				// CallingConvention
				CallingConvention callConv = (CallingConvention)((int)(pinvokeAttributes & PinvokeAttributes.CallConvMask) >> 8);
				if (callConv != 0)
					namedArguments.Add(new CustomAttributeNamedArgument(dllImportType.GetField("CallingConvention"), new CustomAttributeTypedArgument(typeof(CallingConvention), callConv)));
				// CharSet
				CharSet charset = CharSet.None;
				switch (pinvokeAttributes & PinvokeAttributes.CharSetMask)
				{
					case PinvokeAttributes.CharSetAnsi:
						charset = CharSet.Ansi;
						break;
					case PinvokeAttributes.CharSetUnicode:
						charset = CharSet.Unicode;
						break;
					case PinvokeAttributes.CharSetAuto:
						charset = CharSet.Auto;
						break;
				}
				if (charset != CharSet.None)
					namedArguments.Add(new CustomAttributeNamedArgument(dllImportType.GetField("CharSet"), new CustomAttributeTypedArgument(typeof(CharSet), charset)));

				CliCustomAttributeData attribute = new CliCustomAttributeData(
					dllImportType.GetConstructor(new Type[] { typeof(string) }),
					new CustomAttributeTypedArgument[] { new CustomAttributeTypedArgument(typeof(string), moduleName) },
					namedArguments
					);
				attributes.Add(attribute);
			}

			return attributes.ToArray();
		}
		internal bool IsAttributeDefinedOn(int ownerToken, Type attributeType, bool inherit)
		{
			CustomAttributeRow[] attributeRows = this._customAttributeLookup[ownerToken].ToArray();
			CustomAttributeData[] attributes = new CustomAttributeData[attributeRows.Length];
			for (int i = 0; i < attributeRows.Length; i++)
			{
				CustomAttributeData attribute = CliCustomAttributeData.Parse(this, attributeRows[i]);
				if (attributeType.IsAssignableFrom(attribute.Constructor.ReflectedType)
					|| MemberEqualityComparer.Equal(attribute.Constructor.ReflectedType, attributeType)
					)
					return true;
			}

			return false;
		}
		#endregion

		public Assembly ResolveAssemblyRef(int assemblyRefRowNumber)
		{
			if (assemblyRefRowNumber < 1) throw new ArgumentNullException("assemblyRefRowNumber");
			if (assemblyRefRowNumber > this._metadata.AssemblyRefTable.RowCount) throw new ArgumentOutOfRangeException("assemblyRefRowNumber", assemblyRefRowNumber, null);

			AssemblyRefRow assemblyRefRow = this._metadata.AssemblyRefTable[assemblyRefRowNumber - 1];

			AssemblyName assemblyName = assemblyRefRow.ToAssemblyName(this._metadata);
			CliAssembly externalAssembly = this.CliAssembly.LoadContext.LoadAssembly(assemblyName, this.CliAssembly);

			return externalAssembly;
		}

		internal Module ResolveModuleRef(int moduleRefRowNumber)
		{
			if (moduleRefRowNumber < 1) throw new ArgumentNullException("moduleRefRowNumber");
			if (moduleRefRowNumber > this._metadata.ModuleRefTable.RowCount) throw new ArgumentOutOfRangeException("moduleRefRowNumber", moduleRefRowNumber, null);

			ModuleRefRow moduleRefRow = this._metadata.ModuleRefTable[moduleRefRowNumber - 1];
			string moduleName = this._metadata.GetString(moduleRefRow.Name);
			Module module = this.CliAssembly.GetModule(moduleName);

			return module;
		}

		#region Types
		private ILookup<string, int> _typeNameIndex;
		private IDictionary<int, WeakReference> _implementedTypeCache;

		private int GetLocalTokenForType(Type type)
		{
			if ((object)type == null) throw new ArgumentNullException("type");
			if ((object)type.Module == this)
				return type.MetadataToken;

			Type declaringType = type.DeclaringType;
			string matchName;
			int scope;
			if (declaringType != null)
			{
				scope = this.GetLocalTokenForType(declaringType);
				matchName = type.Name;
			}
			else
			{
				matchName = type.FullName;
				if (type.Assembly == this.Assembly)
				{
					scope = this.GetLocalTokenForModule(type.Module);
				}
				else
				{
					scope = this.GetLocalTokenForAssembly(type.Assembly);
				}
			}

			int typeToken = 0;
			MetadataTable<TypeRefRow> typeRefTable = this._metadata.TypeRefTable;
			for (int i = 0; i < typeRefTable.RowCount; i++)
			{
				TypeRefRow typeRefRow = typeRefTable[i];
				string name = typeRefRow.GetFullName(this._metadata);
				if (matchName == name)
				{
					typeToken = CodedIndex.TokenFromTableRow(MetadataTableNumber.TypeRef, i + 1);
				}
			}

			return typeToken;
		}
		private int GetLocalTokenForAssembly(Assembly assembly)
		{
			AssemblyName targetAssemblyName = assembly.GetName();

			int token = 0;
			MetadataTable<AssemblyRefRow> assemblyRefTable = this._metadata.AssemblyRefTable;
			for (int i = 0; i < assemblyRefTable.RowCount; i++)
			{
				AssemblyRefRow assemblyRefRow = assemblyRefTable[i];
				AssemblyName assemblyName = assemblyRefRow.ToAssemblyName(this._metadata);
				if (AssemblyName.ReferenceMatchesDefinition(assemblyName, targetAssemblyName))
				{
					token = new CodedIndex(MetadataTableNumber.AssemblyRef, i + 1).Token;
					break;
				}
			}

			return token;
		}
		private int GetLocalTokenForModule(Module module)
		{
			string targetModuleName = module.ScopeName;

			int token = 0;
			MetadataTable<ModuleRefRow> moduleRefTable = this._metadata.ModuleRefTable;
			for (int i = 0; i < moduleRefTable.RowCount; i++)
			{
				ModuleRefRow moduleRefRow = moduleRefTable[i];
				string moduleName = this._metadata.GetString(moduleRefRow.Name);

				if (moduleName == targetModuleName)
				{
					token = CodedIndex.TokenFromTableRow(MetadataTableNumber.ModuleRef, i + 1);
					break;
				}
			}

			return token;
		}

		public override Type[] GetTypes()
		{
			int typeCount = this._metadata.TypeDefTable.RowCount;
			Type[] types = new Type[typeCount];
			for (int i = 0; i < typeCount; i++)
			{
				Type type = this.LoadTypeDef(i + 1);
				types[i] = type;
			}

			return types;
		}
		public override Type GetType(string className, bool throwOnError, bool ignoreCase)
		{
			int typeDefRowNumber = this.LookupTypeDefRowNumber(className, throwOnError);
			Type type = null;
			if (typeDefRowNumber > 0)
			{
				type = this.LoadTypeDef(typeDefRowNumber);
			}

			if ((object)type == null && throwOnError)
				throw new TypeLoadException();

			return type;
		}
		public override Type ResolveType(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
		{
			CodedIndex typeIndex = CodedIndex.FromToken(metadataToken);

			Type type;
			switch (typeIndex.TableNumber)
			{
				case MetadataTableNumber.TypeDef:
					type = this.LoadTypeDef(typeIndex.RowNumber);
					break;
				case MetadataTableNumber.TypeRef:
					type = this.ResolveTypeRef(typeIndex.RowNumber);
					break;
				case MetadataTableNumber.TypeSpec:
					type = this.ResolveTypeSpec(typeIndex.RowNumber, genericTypeArguments, genericMethodArguments);
					break;
				default:
					throw new ArgumentException(null, "metadataToken");
			}

			//if (
			//    (type != null)
			//    && (type.IsGenericTypeDefinition)
			//    && (genericTypeArguments != null)
			//    && (genericTypeArguments.Length > 0)
			//    )
			//{
			//    type = type.MakeGenericType(genericTypeArguments);
			//}

			return type;
		}

		private Type ResolveTypeSpec(int rowNumber, Type[] genericTypeParameters, Type[] genericMethodParameters)
		{
			if (rowNumber < 1) throw new ArgumentNullException("rowNumber");
			if (rowNumber > this._metadata.TypeSpecTable.RowCount) throw new ArgumentOutOfRangeException("rowNumber", rowNumber, null);

			TypeSpecRow typeSpecRow = this._metadata.TypeSpecTable[rowNumber - 1];
			TypeSpecSignature signature = (TypeSpecSignature)this.GetSignature(typeSpecRow.Signature);
			Type type = signature.Resolve(
				this,
				true,
				genericTypeParameters,
				genericMethodParameters
				);
			return type;
		}

		private bool TryResolveTypeRefToken(
			int typeRefRowNumber,
			out int typeDefToken,
			out Module module
			)
		{
			Type type = this.ResolveTypeRef(typeRefRowNumber);
			if ((object)type != null)
			{
				typeDefToken = type.MetadataToken;
				module = type.Module;
			}
			else
			{
				typeDefToken = 0;
				module = null;
			}

			return (object)type != null;
		}

		private Type ResolveTypeRef(int typeRefRowNumber)
		{
			if (typeRefRowNumber < 1) throw new ArgumentNullException("typeRefRowNumber");
			if (typeRefRowNumber > this._metadata.TypeRefTable.RowCount) throw new ArgumentOutOfRangeException("typeRefRowNumber", typeRefRowNumber, null);

			TypeRefRow typeRefRow = this._metadata.TypeRefTable[typeRefRowNumber - 1];
			CodedIndex resolutionScope = typeRefRow.ResolutionScope;
			string typeNamespace = this._metadata.GetString(typeRefRow.TypeNamespace);
			string typeName = this._metadata.GetString(typeRefRow.TypeName);
			string fullTypeName = (string.IsNullOrEmpty(typeNamespace) ? typeName : string.Format("{0}.{1}", typeNamespace, typeName));

			Type type = null;
			switch (resolutionScope.TableNumber)
			{
				case MetadataTableNumber.Module:
					if (resolutionScope.RowNumber != 1) throw new NotSupportedException();
					type = this.GetType(fullTypeName);
					break;
				case MetadataTableNumber.ModuleRef:
					Module externalModule = this.ResolveModuleRef(resolutionScope.RowNumber);
					if ((object)externalModule != null)
						type = externalModule.GetType(fullTypeName, true);
					break;
				case MetadataTableNumber.AssemblyRef:
					Assembly externalAssembly = this.ResolveAssemblyRef(resolutionScope.RowNumber);
					if ((object)externalAssembly != null)
						type = externalAssembly.GetType(fullTypeName, true);
					break;
				case MetadataTableNumber.TypeRef:
					Type parentType = this.ResolveTypeRef(resolutionScope.RowNumber);
					if ((object)parentType != null)
						type = parentType.GetNestedType(typeName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
					break;
			}

			return type;
		}

		private int LookupTypeDefRowNumber(string name, bool throwOnError)
		{
			int typeDefRowNumber = this._typeNameIndex[name].FirstOrDefault();
			if (typeDefRowNumber == 0 && throwOnError)
				throw new TypeLoadException();

			return typeDefRowNumber;
		}
		public int LookupTypeDefToken(string name, bool throwOnError)
		{
			int typeDefRowNumber = this.LookupTypeDefRowNumber(name, throwOnError);
			return CodedIndex.TokenFromTableRow(MetadataTableNumber.TypeDef, typeDefRowNumber);
		}

		public ModuleMemberReference ResolveTypeRefToken(int typeRefToken)
		{
			CodedIndex index = CodedIndex.FromToken(typeRefToken);
			if (index.TableNumber != MetadataTableNumber.TypeRef)
				throw new ArgumentException("The token must be a TypeRef token.", "typeRefToken");
			if (index.IsNull)
				throw new ArgumentNullException("typeRefToken");

			if (index.RowNumber > this._metadata.TypeRefTable.RowCount) throw new ArgumentOutOfRangeException("typeRefToken", index.RowNumber, null);

			TypeRefRow typeRefRow = this._metadata.TypeRefTable[index.RowNumber - 1];
			CodedIndex resolutionScope = typeRefRow.ResolutionScope;
			string typeNamespace = this._metadata.GetString(typeRefRow.TypeNamespace);
			string typeName = this._metadata.GetString(typeRefRow.TypeName);
			string fullTypeName = (string.IsNullOrEmpty(typeNamespace) ? typeName : string.Format("{0}.{1}", typeNamespace, typeName));

			ModuleMemberReference typeReference = ModuleMemberReference.Null;
			switch (resolutionScope.TableNumber)
			{
				case MetadataTableNumber.Module:
					if (resolutionScope.RowNumber != 1) throw new NotSupportedException();
					typeReference = ModuleMemberReference.FromModuleToken(this, this.LookupTypeDefToken(fullTypeName, false));
					break;
				case MetadataTableNumber.ModuleRef:
					CliModule externalModule = this.ResolveModuleRef(resolutionScope.RowNumber) as CliModule;
					if (externalModule != null)
						typeReference = ModuleMemberReference.FromModuleToken(externalModule, externalModule.LookupTypeDefToken(fullTypeName, false));
					break;
				case MetadataTableNumber.AssemblyRef:
					CliAssembly externalAssembly = this.ResolveAssemblyRef(resolutionScope.RowNumber) as CliAssembly;
					if ((object)externalAssembly != null)
						typeReference = externalAssembly.LookupTypeDefToken(fullTypeName, false);
					break;
				case MetadataTableNumber.TypeRef:
					ModuleMemberReference parentTypeDefReference = this.ResolveTypeRefToken(resolutionScope.Token);
					if (parentTypeDefReference.Module != null)
						typeReference = new ModuleMemberReference(((Type)parentTypeDefReference.LoadMember()).GetNestedType(typeName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly));
					break;
			}

			return typeReference;
		}

		private CliImplementedType GetTypeDefOwner(int rowNumber)
		{
			int ownerRowNumber = this._metadata.TypeDef_MethodDef.FindOwnerRowNumber(rowNumber);
			CliImplementedType declaringType = (ownerRowNumber > 0)
				? this.LoadTypeDef(ownerRowNumber)
				: null
				;
			return declaringType;
		}

		private CliImplementedType GetCachedType(int typeDefNumber)
		{
			CliImplementedType implementedType;
			//lock (this._implementedTypeCache)
			{
				WeakReference weakRef;
				if (
					this._implementedTypeCache.TryGetValue(typeDefNumber, out weakRef)
					&& ((implementedType = (CliImplementedType)weakRef.Target) != null)
					)
					return implementedType;
			}

			return null;
		}
		private CliImplementedType LoadTypeDef(
			int rowNumber
			)
		{
			CliImplementedType implementedType = this.GetCachedType(rowNumber);
			if (implementedType != null)
				return implementedType;

			NestedClassRow nestedRow = this._nestedClassSearcher.FindItem(rowNumber);
			Type declaringType = (nestedRow != null)
				? this.LoadTypeDef(nestedRow.EnclosingClass.RowNumber)
				: null
				;

			return this.LoadTypeDef(rowNumber, declaringType, declaringType);
		}
		private CliImplementedType LoadTypeDef(
			int rowNumber,
			Type declaringType,
			Type reflectedType
			)
		{
			if (rowNumber < 1) throw new ArgumentNullException("rowNumber");
			if (rowNumber > this._metadata.TypeDefTable.RowCount) throw new ArgumentOutOfRangeException("rowNumber", rowNumber, null);

			bool isCanonical = object.ReferenceEquals(declaringType, reflectedType)
				&& (
					object.ReferenceEquals(declaringType, null)
					|| !declaringType.IsGenericType
					|| declaringType.IsGenericTypeDefinition
					);
			if (isCanonical)
			{
				CliImplementedType cachedType = this.GetCachedType(rowNumber);
				if (cachedType != null)
					return cachedType;
			}

			TypeDefRow typeDef = this._metadata.TypeDefTable[rowNumber - 1];

			CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(
				this,
				CodedIndex.TokenFromTableRow(MetadataTableNumber.TypeDef, rowNumber)
				)
			{
				reflectedType = reflectedType,
				declaringType = declaringType
			};
			CliType type = new CliType(typeDef, basicInfo);

			if (isCanonical)
			{
				lock (this._implementedTypeCache)
				{
					this._implementedTypeCache[rowNumber] = new WeakReference(type);
				}
			}

			return type;
		}
		#endregion
		#region Methods
		private Type GetMethodOwner(
			CodedIndex methodToken,
			Type[] genericTypeArguments,
			Type[] genericMethodArguments
			)
		{
			Type owner;
			switch (methodToken.TableNumber)
			{
				case MetadataTableNumber.MethodDef:
					owner = this.GetMethodDefOwner(methodToken.RowNumber);
					break;
				case MetadataTableNumber.MethodRef:
					owner = this.GetMethodRefOwner(
						methodToken.RowNumber,
						genericTypeArguments,
						genericMethodArguments
						);
					break;
				default:
					throw new ArgumentException();
			}

			return owner;
		}
		private Type GetMethodRefOwner(
			int methodRefRowNumber,
			Type[] genericTypeArguments,
			Type[] genericMethodArguments
			)
		{
			MemberRefRow methodRef = this._metadata.MemberRefTable[methodRefRowNumber - 1];
			Type declaringType = this.ResolveType(methodRef.Class.Token, genericTypeArguments, genericMethodArguments);
			return declaringType;
		}
		private int GetMethodDefOwnerRowNumber(int rowNumber)
		{
			int ownerRowNumber = this._metadata.TypeDef_MethodDef.FindOwnerRowNumber(rowNumber);
			return ownerRowNumber;
		}
		private CliImplementedType GetMethodDefOwner(int rowNumber)
		{
			int ownerRowNumber = this.GetMethodDefOwnerRowNumber(rowNumber);
			CliImplementedType declaringType = (ownerRowNumber > 0)
				? this.LoadTypeDef(ownerRowNumber)
				: null
				;
			return declaringType;
		}

		private Dictionary<int, WeakReference> _implementedMethodCache;

		private MethodBase GetCachedMethodDef(int methodDefNumber)
		{
			MethodBase method = null;
			WeakReference weakRef;

			if (this._implementedMethodCache.TryGetValue(methodDefNumber, out weakRef))
				method = (MethodBase)weakRef.Target;

			return method;
		}
		private MethodBase LoadMethodDef(
			int methodDefRowNumber
			)
		{
			MethodBase method = this.GetCachedMethodDef(methodDefRowNumber);
			if (method != null)
				return method;

			Type declaringType = this.GetMethodDefOwner(methodDefRowNumber);
			//if (
			//    (declaringType.IsGenericTypeDefinition)
			//    && (genericTypeArguments != null)
			//    )
			//    declaringType = declaringType.MakeGenericType(genericTypeArguments);

			method = this.LoadMethodDef(
				methodDefRowNumber,
				declaringType,
				declaringType
				);

			return method;
		}
		internal MethodBase LoadMethodDef(
			int methodDefRowNumber,
			Type declaringType,
			Type reflectedType
			)
		{
			if (methodDefRowNumber < 1) throw new ArgumentNullException("methodDefRowNumber");
			if (methodDefRowNumber > this._metadata.MethodDefTable.RowCount) throw new ArgumentOutOfRangeException("methodDefRowNumber", methodDefRowNumber, null);

			bool isCanonical = object.ReferenceEquals(declaringType, reflectedType)
				&& (
					object.ReferenceEquals(declaringType, null)
					|| !declaringType.IsGenericType
					|| declaringType.IsGenericTypeDefinition
					);
			if (isCanonical)
			{
				//lock (this._implementedMethodCache)
				{
					MethodBase method = this.GetCachedMethodDef(methodDefRowNumber);
					if (method != null)
						return method;
				}
			}

			MethodDefRow methodDef = this._metadata.MethodDefTable[methodDefRowNumber - 1];

			CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(this, CodedIndex.TokenFromTableRow(MetadataTableNumber.MethodDef, methodDefRowNumber))
			{
				declaringType = declaringType,
				reflectedType = reflectedType
			};
			string name = this._metadata.GetString(methodDef.Name);
			bool isConstructor =
				(name == ".ctor")
				|| (name == ".cctor")
				;
			MethodBase methodBase = isConstructor
				? (MethodBase)new CliConstructorInfo(methodDef, basicInfo, name)
				: (MethodBase)new CliMethodInfo(methodDef, basicInfo, name)
				;

			if (isCanonical)
			{
				lock (this._implementedMethodCache)
				{
					this._implementedMethodCache[methodDefRowNumber] = new WeakReference(methodBase);
				}
			}

			return methodBase;
		}
		internal MethodBody LoadMethodBody(
			MethodBase method,
			uint rva
			//Type[] genericTypeArgs,
			//Type[] genericMethodArgs
			)
		{
			if (method == null) throw new ArgumentNullException("method");
			if (rva == 0) return null;
			uint? offset = this.peModule.RvaToFileOffset(rva);
			if (!offset.HasValue) return null;
			using (Stream stream = this.peModule.GetStream())
			{
				BinaryReader reader = new BinaryReader(stream);

				stream.Position = offset.Value;
				MethodBodyFlags flags = (MethodBodyFlags)stream.ReadByte();

				int methodBodyLength;
				CliLocalInfo[] locals;
				ExceptionHandlingClause[] clauses = null;
				int maxStackSize;
				int localSignatureToken = 0;
				if (flags.HasFlag(MethodBodyFlags.HasFatHeader))
				{
					byte b = (byte)stream.ReadByte();
					flags |= (MethodBodyFlags)((b & 0x0F) << 8);
					int headerLengthBytes = (b & 0xF0) >> 2;
					maxStackSize = reader.ReadUInt16();
					methodBodyLength = reader.ReadInt32();

					localSignatureToken = reader.ReadInt32();
					if (localSignatureToken != 0)
					{
						LocalVariableSignature localVariableSignature = SignatureReader.ReadLocalVariablesSignature(this.ResolveSignature(localSignatureToken));
						locals = new CliLocalInfo[localVariableSignature.variables.Length];
						for (int i = 0; i < locals.Length; i++)
						{
							VariableSignature variableSignature = localVariableSignature.variables[i];
							//Type variableType = variableSignature.ResolveType(this, genericTypeArgs, genericMethodArgs);
							CliLocalInfo localInfo = new CliLocalInfo(
								localVariableSignature.variables[i],
								method,
								i,
								variableSignature.IsPinned
								);
							locals[i] = localInfo;
						}
					}
					else
					{
						locals = new CliLocalInfo[0];
					}
				}
				else if (flags.HasFlag(MethodBodyFlags.HasTinyHeader))
				{
					maxStackSize = 8;
					locals = new CliLocalInfo[0];
					methodBodyLength = ((int)flags) >> 2;
				}
				else
				{
					return null;
				}

				byte[] bytes = new byte[methodBodyLength];
				stream.Read(bytes, 0, bytes.Length);

				if (flags.HasFlag(MethodBodyFlags.MoreSections | MethodBodyFlags.HasFatHeader))
				{
					long position = stream.Position;
					if ((position % 4) != 0)
						stream.Position += (4 - (position % 4));

					uint sectionHeader = reader.ReadUInt32();
					MethodDataSectionFlags sectionFlags = (MethodDataSectionFlags)(sectionHeader & 0xFF);
					bool isSectionFat = sectionFlags.HasFlag(MethodDataSectionFlags.FatFormat);
					int sectionLength = (int)(sectionHeader >> 8);
					//if (isSectionFat)
					//    sectionLength =
					//        stream.ReadByte()
					//        | (stream.ReadByte() << 8)
					//        | (stream.ReadByte() << 16)
					//        ;
					//else
					//    sectionLength = stream.ReadByte();

					if (sectionFlags.HasFlag(MethodDataSectionFlags.ExceptionHandlerTable))
					{
						if (isSectionFat)
						{
							int clauseCount = (sectionLength - 4) / 24;
							clauses = new ExceptionHandlingClause[clauseCount];
							for (int i = 0; i < clauseCount; i++)
							{
								ExceptionHandlingClauseOptions options = (ExceptionHandlingClauseOptions)reader.ReadInt32();
								int tryOffset = reader.ReadInt32();
								int tryLength = reader.ReadInt32();
								int handlerOffset = reader.ReadInt32();
								int handlerLength = reader.ReadInt32();
								int classToken = 0;
								int filterOffset = 0;
								if (options == ExceptionHandlingClauseOptions.Clause)
									classToken = reader.ReadInt32();
								else
									filterOffset = reader.ReadInt32();

								ExceptionHandlingClause clause = new CliExceptionHandlingClause(
									this,
									tryOffset,
									tryLength,
									filterOffset,
									classToken,
									handlerOffset,
									handlerLength
									);
								clauses[i] = clause;
							}
						}
						else
						{
							int clauseCount = (sectionLength - 4) / 12;
							clauses = new ExceptionHandlingClause[clauseCount];
							for (int i = 0; i < clauseCount; i++)
							{
								ExceptionHandlingClauseOptions options = (ExceptionHandlingClauseOptions)reader.ReadUInt16();
								int tryOffset = reader.ReadInt16();
								int tryLength = reader.ReadByte();
								int handlerOffset = reader.ReadInt16();
								int handlerLength = reader.ReadByte();

								int classToken = 0;
								int filterOffset = 0;
								if (options == ExceptionHandlingClauseOptions.Clause)
									classToken = reader.ReadInt32();
								else
									filterOffset = reader.ReadInt32();

								ExceptionHandlingClause clause = new CliExceptionHandlingClause(
									this,
									tryOffset,
									tryLength,
									filterOffset,
									classToken,
									handlerOffset,
									handlerLength
									);
								clauses[i] = clause;
							}
						}
					}
				}

				if (clauses == null)
					clauses = new ExceptionHandlingClause[0];

				CliMethodBody body = new CliMethodBody(
					maxStackSize,
					flags.HasFlag(MethodBodyFlags.InitLocals),
					bytes,
					localSignatureToken,
					locals,
					clauses
					);

				return body;
			}
		}
		#endregion
		#region Events
		private EventInfo LoadEvent(
			int eventRowNumber
			)
		{
			int eventMapRowNumber = this.Metadata.EventMap_Event.FindOwnerRowNumber(eventRowNumber);
			EventMapRow eventMapRow = this.Metadata.EventMapTable[eventMapRowNumber - 1];
			Type declaringType = this.ResolveType(eventMapRow.Parent.Token);
			return this.LoadEvent(eventRowNumber, declaringType, declaringType);
		}
		private EventInfo LoadEvent(
			int eventRowNumber,
			Type declaringType,
			Type reflectedType
			)
		{
			if (eventRowNumber < 1) throw new ArgumentNullException("eventRowNumber");
			if (eventRowNumber > this._metadata.EventTable.RowCount) throw new ArgumentOutOfRangeException("eventRowNumber", eventRowNumber, null);
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			EventRow eventRow = this._metadata.EventTable[eventRowNumber - 1];
			int ownerRowNumber = this._metadata.EventMap_Event.FindOwnerRowNumber(eventRowNumber);

			CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(this, CodedIndex.TokenFromTableRow(MetadataTableNumber.Event, eventRowNumber))
			{
				declaringType = declaringType,
				reflectedType = reflectedType
			};
			CliEventInfo eventInfo = new CliEventInfo(eventRow, basicInfo);
			return eventInfo;
		}
		#endregion
		#region Properties
		private PropertyInfo LoadProperty(
			int propertyRowNumber
			)
		{
			int propertyMapRowNumber = this.Metadata.PropertyMap_Property.FindOwnerRowNumber(propertyRowNumber);
			PropertyMapRow propertyMapRow = this.Metadata.PropertyMapTable[propertyMapRowNumber - 1];
			Type declaringType = this.ResolveType(propertyMapRow.Parent.Token);
			return this.LoadProperty(propertyRowNumber, declaringType, declaringType);
		}
		private PropertyInfo LoadProperty(
			int propertyRowNumber,
			Type declaringType,
			Type reflectedType
			)
		{
			if (propertyRowNumber < 1) throw new ArgumentNullException("propertyRowNumber");
			if (propertyRowNumber > this._metadata.PropertyTable.RowCount) throw new ArgumentOutOfRangeException("propertyRowNumber", propertyRowNumber, null);
			if ((object)declaringType == null) throw new ArgumentNullException("declaringType");
			if ((object)reflectedType == null) throw new ArgumentNullException("reflectedType");

			PropertyRow propertyRow = this._metadata.PropertyTable[propertyRowNumber - 1];
			int ownerRowNumber = this._metadata.PropertyMap_Property.FindOwnerRowNumber(propertyRowNumber);

			CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(this, CodedIndex.TokenFromTableRow(MetadataTableNumber.Property, propertyRowNumber))
			{
				declaringType = declaringType,
				reflectedType = reflectedType,
			};
			CliPropertyInfo propertyInfo = new CliPropertyInfo(propertyRow, basicInfo);
			return propertyInfo;
		}
		#endregion
		#region Fields
		private CliImplementedType GetFieldOwner(int rowNumber)
		{
			int ownerRowNumber = this._metadata.TypeDef_Field.FindOwnerRowNumber(rowNumber);
			CliImplementedType declaringType = (ownerRowNumber > 0)
				? this.LoadTypeDef(ownerRowNumber)
				: null
				;
			return declaringType;
		}
		private FieldInfo LoadField(
			int fieldRowNumber
			)
		{
			Type declaringType = this.GetFieldOwner(fieldRowNumber);
			return this.LoadField(
				fieldRowNumber,
				declaringType,
				declaringType
				);
		}
		private FieldInfo LoadField(
			int fieldRowNumber,
			Type declaringType,
			Type reflectedType
			)
		{
			if (fieldRowNumber < 1) throw new ArgumentNullException("fieldRowNumber");
			if (fieldRowNumber > this._metadata.FieldTable.RowCount) throw new ArgumentOutOfRangeException("fieldRowNumber", fieldRowNumber, null);

			FieldRow fieldRow = this._metadata.FieldTable[fieldRowNumber - 1];

			CliMemberBasicInfo basicInfo = new CliMemberBasicInfo(this, CodedIndex.TokenFromTableRow(MetadataTableNumber.Field, fieldRowNumber))
			{
				declaringType = declaringType,
				reflectedType = reflectedType
			};
			CliFieldInfo field = new CliFieldInfo(fieldRow, basicInfo);
			return field;
		}
		#endregion

		public override MethodBase ResolveMethod(
			int metadataToken,
			Type[] genericTypeArguments,
			Type[] genericMethodArguments
			)
		{
			CodedIndex methodIndex = CodedIndex.FromToken(metadataToken);

			MethodBase method = null;
			switch (methodIndex.TableNumber)
			{
				case MetadataTableNumber.MethodDef:
					method = this.LoadMethodDef(methodIndex.RowNumber);
					break;
				case MetadataTableNumber.MethodRef:
					method = ResolveMethodRef(genericTypeArguments, genericMethodArguments, methodIndex.RowNumber);
					break;
				case MetadataTableNumber.MethodSpec:
					{
						MethodSpecRow methodSpecRow = this._metadata.MethodSpecTable[methodIndex.RowNumber - 1];
						MethodSpec methodSpec = (MethodSpec)this.GetSignature(methodSpecRow.Instantiation);
						Type[] methodTypeArgs = new Type[methodSpec.genericArguments.Length];
						for (int i = 0; i < methodTypeArgs.Length; i++)
						{
							methodTypeArgs[i] = methodSpec.genericArguments[i].ResolveType(
								this,
								true,
								genericTypeArguments,
								genericMethodArguments
								);
						}
						MethodInfo methodDefinition = (MethodInfo)this.ResolveMethod(methodSpecRow.Method.Token, genericTypeArguments, genericMethodArguments);
						//MethodInfo methodDefinition = (MethodInfo)this.ResolveMethod(methodSpecRow.Method.Token, genericTypeArguments, genericMethodArguments);
						method = methodDefinition.MakeGenericMethod(methodTypeArgs);
						//method = ((MethodSpecSignature)methodSpecRow.Instantiation.Signature).ResolveMethod(methodDefinition, genericTypeArguments, methodTypeArgs);
					}
					break;
				default:
					throw new ArgumentException();
					break;
			}

			return method;
		}

		private Dictionary<int, ModuleMemberReference> _methodRefCache;
		private MethodBase ResolveMethodRef(Type[] genericTypeArguments, Type[] genericMethodArguments, int methodRefRowNumber)
		{
			MethodBase method = null;

			bool isCanonical = (
				((genericTypeArguments == null) || (genericTypeArguments.Length == 0))
				//&& ((genericMethodArguments == null) || (genericMethodArguments.Length == 0))
				);
			if (isCanonical)
			{
				ModuleMemberReference memberRef;
				if (this._methodRefCache.TryGetValue(methodRefRowNumber, out memberRef))
					return (MethodBase)memberRef.LoadMember();
			}

			MemberRefRow methodRef = this._metadata.MemberRefTable[methodRefRowNumber - 1];
			Type declaringType = this.ResolveType(methodRef.Class.Token, genericTypeArguments, genericMethodArguments);
			MethodRefSignature methodRefSig = (MethodRefSignature)this._metadata.GetSignature(methodRef.Signature);
			if ((object)declaringType != null)
			{
				string targetName = this._metadata.GetString(methodRef.Name);
				bool isConstructor = (targetName == ".ctor");
				MethodBase[] candidates;
				if (isConstructor)
				{
					candidates = declaringType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
				}
				else
				{
					CliTypeBase cliType = declaringType as CliTypeBase;
					if ((object)cliType != null)
					{
						candidates = cliType.GetDeclaredMethods();
					}
					else
					{
						BindingFlags flags = (methodRefSig.flags.HasFlag(SignatureFlags.HasThis) ? BindingFlags.Instance : BindingFlags.Static)
							| BindingFlags.DeclaredOnly
							| BindingFlags.Public
							| BindingFlags.NonPublic
							;
						candidates = declaringType.GetMethods(flags);
					}
				}

				Type[] contextGenericArgs = declaringType.GetGenericArguments();
				if (
					(genericTypeArguments != null)
					&& (genericTypeArguments.Length > 0)
					)
					contextGenericArgs = contextGenericArgs.Concat(genericTypeArguments).ToArray();
				foreach (var candidate in candidates)
				{
					if (candidate.Name != targetName)
						continue;

					int genericArgCount = 0;
					MethodInfo candidateMethod = candidate as MethodInfo;
					if (
						((object)candidateMethod != null)
						&& (candidateMethod.IsGenericMethod)
						)
						genericArgCount = candidateMethod.GetGenericArguments().Length;

					if (genericArgCount != methodRefSig.genericParameterCount)
						continue;

					CliImplementedMethodInfo candidateCliMethod = candidateMethod as CliImplementedMethodInfo;
					bool matches;
					if ((object)candidateCliMethod != null)
					{
						matches = methodRefSig.Matches(this, candidateCliMethod.GetSignature(), candidateCliMethod.Module);
					}
					else
					{
						matches = methodRefSig.Matches(candidate, this, contextGenericArgs, genericMethodArguments);
					}

					if (matches)
					{
						method = candidate;
						break;
					}
				}
			}

			if ((object)method != null)
			{
				if (isCanonical && !CodedIndex.IsNullToken(method.MetadataToken))
				{
					lock (this._methodRefCache)
					{
						this._methodRefCache[methodRefRowNumber] = new ModuleMemberReference(method);
					}
				}
			}
			else
			{
				method = new CliUnresolvedMethodInfo(
					new CliMemberBasicInfo(this, CodedIndex.TokenFromTableRow(MetadataTableNumber.MemberRef, methodRefRowNumber))
					{
						declaringType = declaringType,
						reflectedType = declaringType
					},
					methodRefSig,
					this._metadata.GetString(methodRef.Name)
					);
			}

			return method;
		}

		public override FieldInfo ResolveField(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
		{
			MetadataTableNumber tableNumber = (CodedIndex.FromToken(metadataToken).TableNumber);

			CodedIndex index = CodedIndex.FromToken(metadataToken);
			FieldInfo field;
			switch (index.TableNumber)
			{
				case MetadataTableNumber.Field:
					field = this.LoadField(index.RowNumber);
					break;
				case MetadataTableNumber.FieldRef:
					MemberRefRow memberRefRow = this._metadata.MemberRefTable[index.RowNumber - 1];
					Type type = this.ResolveType(memberRefRow.Class.Token, genericTypeArguments, genericMethodArguments);
					if ((object)type == null)
						return null;
					field = type.GetField(this._metadata.GetString(memberRefRow.Name), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					break;
				default:
					throw new ArgumentException(null, "metadataToken");
			}

			return field;
		}
		private MemberInfo ResolveMemberRef(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
		{
			CodedIndex index = CodedIndex.FromToken(metadataToken);
			MemberRefRow row = this._metadata.MemberRefTable[index.RowNumber - 1];
			byte[] data = this._metadata.GetBlob(row.Signature);
			if (data.FirstOrDefault() == (byte)SignatureFlags.Field)
				return this.ResolveField(metadataToken, genericTypeArguments, genericMethodArguments);
			else
				return this.ResolveMethod(metadataToken, genericTypeArguments, genericMethodArguments);
		}
		public override MemberInfo ResolveMember(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
		{
			MetadataTableNumber tableNumber = (CodedIndex.FromToken(metadataToken).TableNumber);

			MemberInfo member;
			switch (tableNumber)
			{
				case MetadataTableNumber.MethodDef:
				case MetadataTableNumber.MethodSpec:
					member = this.ResolveMethod(metadataToken, genericTypeArguments, genericMethodArguments);
					break;
				case MetadataTableNumber.TypeDef:
				case MetadataTableNumber.TypeRef:
				case MetadataTableNumber.TypeSpec:
					member = this.ResolveType(metadataToken, genericTypeArguments, genericMethodArguments);
					break;
				case MetadataTableNumber.Field:
					member = this.ResolveField(metadataToken, genericTypeArguments, genericMethodArguments);
					break;
				case MetadataTableNumber.MemberRef:
					member = this.ResolveMemberRef(metadataToken, genericTypeArguments, genericMethodArguments);
					break;
				case MetadataTableNumber.Property:
					member = this.LoadProperty(CodedIndex.RowNumberFromToken(metadataToken));
					break;
				case MetadataTableNumber.Event:
					member = this.LoadEvent(CodedIndex.RowNumberFromToken(metadataToken));
					break;
				default:
					throw new ArgumentException(null, "metadataToken");
			}

			return member;
		}

		private Dictionary<int, WeakReference> _signatureTable;

		internal Signature GetSignature(SignatureRef signatureRef)
		{
			Signature signature;

			WeakReference weakRef;
			if (
				this._signatureTable.TryGetValue(signatureRef.HeapIndex, out weakRef)
				&& ((signature = (Signature)weakRef.Target) != null)
				)
			{
			}
			else
			{
				signature = SignatureReader.ReadSignature(signatureRef.SignatureKind, this._metadata.GetBlob(signatureRef));

				lock (this._signatureTable)
					this._signatureTable[signatureRef.HeapIndex] = new WeakReference(signature);
			}

			return signature;
		}
		public override byte[] ResolveSignature(int metadataToken)
		{
			CodedIndex index = CodedIndex.FromToken(metadataToken);
			switch (index.TableNumber)
			{
				case MetadataTableNumber.StandAloneSig:
					return this._metadata.GetBlob(this._metadata.StandAloneSigTable.Rows[index.RowNumber - 1].Signature);
					break;
			}

			return null;
		}
		public override string ResolveString(int metadataToken)
		{
			if ((metadataToken & 0xFF000000) != 0x70000000) throw new ArgumentException("Not a valid string token", "metadataToken");
			string str = this._metadata.GetUserString(metadataToken & 0x00FFFFFF);
			return str;
		}
	}
}
